Back to Blog Tutorial

Building a Detail Screen: Using Only HTML Structure and SQL

Lists and tables render many rows, deliberately fenced off from the rest of the page. A detail screen is the opposite case: exactly one record, and its values need to reach further than a single template.

May 2005·5 min read·Post 5 of 8

Click a contact in Post 3's grid or Post 4's table, and you'd expect a detail screen showing that one person in full. This is the simplest kind of block on the platform, precisely because there's no repetition to manage — one query, one row, values available wherever the block needs them.

1 The XML

No dynamiclist, no ListRow — a detail block is just a container with its own SQL:

emxml_blkContactDetail.xml
<Block tag='div' class='detail-card'>

    <Element type='inlinecontainer' tag='div'>
        <Attributes class='detail-header' />
        <Element type='htmlitem' tag='div'>
            <Attributes class='detail-avatar' />
            <Text>%initials%</Text>
        </Element>
        <Element type='html'>
            <Content><![CDATA[
                <div>
                    <div class='detail-name'>%contact_name%</div>
                    <div class='detail-role'>%job_title% · %department%</div>
                </div>
            ]]></Content>
        </Element>
        <Element type='htmlitem' tag='div'>
            <Attributes class='detail-status' />
            <Text>%status_label%</Text>
        </Element>
    </Element>

    <Element type='html'>
        <Content><![CDATA[
            <div class='detail-body'>
                <div><div class='detail-field-label'>Email</div>
                    <div class='detail-field-value'>%email%</div></div>
                <div><div class='detail-field-label'>Phone</div>
                    <div class='detail-field-value'>%phone%</div></div>
                <div><div class='detail-field-label'>Open Orders</div>
                    <div class='detail-field-value'>%open_order_count%</div></div>
                <div><div class='detail-field-label'>Customer Since</div>
                    <div class='detail-field-value'>%customer_since%</div></div>
            </div>
        ]]></Content>
    </Element>

    <!-- block-level SQL — single row only -->
    <SQL>
        <query name='contact_detail'>
            <![CDATA[
                SELECT c.contact_name, c.job_title, c.department, c.email, c.phone,
                       UPPER(LEFT(c.contact_name,1)) AS initials,
                       DATE_FORMAT(c.created_date,'%%b %%Y') AS customer_since,
                       IF(c.status='Active','Active','Inactive') AS status_label,
                       COUNT(o.order_id) AS open_order_count
                FROM   contacts c
                LEFT JOIN orders o ON o.contact_id = c.contact_id AND o.status = 'open'
                WHERE  c.contact_id = '%contact_id%'
                AND    c.dept_id = '%deptid%'
                GROUP BY c.contact_id
                LIMIT  1
            ]]>
        </query>
    </SQL>
</Block>

What's different about single-row data

<Block> root
Every block file's root element is <Block>, not an ordinary <Element>. It's what makes a block-level <SQL> block legal at all — <SQL> can only sit as a direct child of <Block> (or of <Element type='form'>, as post 6 will show) — never as a child of a plain inlinecontainer.
no ListRow / TableRow
There's nothing to repeat, so there's no row template — just an ordinary container with its columns referenced as tokens directly.
%contact_id%
Where the "which contact" value comes from — passed in as a parameter when this block is opened (a click on a card or table row, carrying that row's id along with it).
tokens travel further
Because this is a single-row query, its columns become ordinary tokens usable anywhere in the block — unlike list and table data, which stays scoped to its own row template and goes no further.
LIMIT 1
Worth keeping even though contact_id should already be unique — it's a safety net, not a formality, since a query returning more than one row here would only ever use the first regardless.
2 Rendered live
app.example.com/manager/contacts/1042
S
Sarah Whitfield
Operations Manager · Operations
Active
Email
s.whitfield@example.com
Phone
+44 1895 700 142
Open Orders
3
Customer Since
Mar 2023
Same JOIN pattern as the table post, same tenant scoping, same one-query rule — the only real change is LIMIT 1 and the fact that nothing here is wrapped in a row template.

The pattern so far

Three posts in, and the shape is already clear: every read operation on the platform is the same idea — a query, a template, tokens standing in for columns — adjusted for how many rows come back and how they need to be laid out. Many free-form rows: dynamiclist. Many structured rows: dynamiclisttable. Exactly one row, values used freely: block-level SQL, no wrapper at all. Reading data was never the hard part of building a web application — it's writing it back that usually is, which is where the next two posts go.